﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Reflection.Emit;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Text;

namespace KnBinarySerializer.Field
{
    internal class MethodHelper
    {
        public static Func<T> CreateDefaultConstructor<T>(Type type)
        {
            Type delegateType = typeof(T);
            DynamicMethod method = CreateDynamicMethod("Create" + type.FullName, delegateType, Type.EmptyTypes, type);
            method.InitLocals = true;
            ILGenerator generator = method.GetILGenerator();

            if (type.IsValueType)
            {
                generator.DeclareLocal(type);
                generator.Emit(OpCodes.Ldloc_0);

                // only need to box if the delegate isn't returning the value type
                if (type != delegateType)
                    generator.Emit(OpCodes.Box, type);
            }
            else
            {
                ConstructorInfo constructorInfo =
                    type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic
                    | BindingFlags.Instance, null, Type.EmptyTypes, null);

                if (constructorInfo == null)
                    throw new ArgumentException("Could not get constructor for [0].".FormatWith(type.FullName));
                generator.Emit(OpCodes.Newobj, constructorInfo);
            }
            generator.Emit(OpCodes.Ret);
            return (Func<T>)method.CreateDelegate(typeof(Func<T>));
        }
        public static Func<T, object> CreateGet<T>(PropertyInfo propertyInfo)
        {
            DynamicMethod method = CreateDynamicMethod($"Get{propertyInfo.Name}", typeof(object), new[] { typeof(T) }, propertyInfo.DeclaringType);
            ILGenerator il = method.GetILGenerator();

            MethodInfo get = propertyInfo.GetGetMethod(true);
            if (get == null)
                throw new ArgumentException(string.Format("属性{0}不存在", propertyInfo.Name));

            if (!get.IsStatic)
                il.PushInstance(propertyInfo.DeclaringType);

            il.CallMethod(get);
            il.BoxIfNeeded(propertyInfo.PropertyType);
            il.Emit(OpCodes.Ret);
            return (Func<T, object>)method.CreateDelegate(typeof(Func<T, object>));
        }

        public static Action<T, object> CreateSet<T>(PropertyInfo propertyInfo)
        {
            MethodInfo set = propertyInfo.GetSetMethod(true);
            if (set == null)
                return null;
            DynamicMethod method = CreateDynamicMethod($"Set{propertyInfo.Name}", null, new[] {typeof(T),typeof(object)}, propertyInfo.DeclaringType);
            ILGenerator il = method.GetILGenerator();            
            
            if(!set.IsStatic)
                il.PushInstance(propertyInfo.DeclaringType);
            il.Emit(OpCodes.Ldarg_1);
            il.UnboxIfNeeded(propertyInfo.PropertyType);
            il.CallMethod(set);
            il.Emit(OpCodes .Ret);

            return (Action<T,object>)method.CreateDelegate(typeof (Action<T, object>));

        }

        public static Func<T,object> CreateGet<T>(FieldInfo fieldInfo)
        {
            if (fieldInfo.IsLiteral)
            {
                object val = fieldInfo.GetValue(null);
                Func<T, object> getter = o => val;
                return getter;
            }
            DynamicMethod method = CreateDynamicMethod($"Get{fieldInfo.Name}", typeof(T), new[] { typeof(object) }, fieldInfo.DeclaringType);
            ILGenerator il = method.GetILGenerator();

            if (!fieldInfo.IsStatic)
            {
                il.PushInstance(fieldInfo.DeclaringType);
                il.Emit(OpCodes.Ldfld, fieldInfo);
            }
            else
            {
                il.Emit(OpCodes.Ldsfld, fieldInfo);
            }
            il.BoxIfNeeded(fieldInfo.FieldType);
            il.Emit(OpCodes.Ret);
            return (Func<T, object>) method.CreateDelegate(typeof(Func<T, object>));
        }
        public static Action<T,object> CreateSet<T>(FieldInfo fieldInfo)
        {
            DynamicMethod dynMethod = CreateDynamicMethod($"Set{fieldInfo.Name}", null, new[] { typeof(T), typeof(object) }, fieldInfo.DeclaringType);
            ILGenerator il = dynMethod.GetILGenerator();

            if (!fieldInfo.IsStatic)
                il.PushInstance(fieldInfo.DeclaringType);
            il.Emit(OpCodes.Ldarg_1);
            il.UnboxIfNeeded(fieldInfo.FieldType);
            if (fieldInfo.IsStatic)
                il.Emit(OpCodes.Stsfld, fieldInfo);
            else
                il.Emit(OpCodes.Stfld, fieldInfo);
            il.Emit(OpCodes.Ret);

            return (Action<T,object>) dynMethod.CreateDelegate(typeof(Action<T,object>));
        }

        public static DynamicMethod CreateDynamicMethod(string name,Type returnType, Type[] parameterTypes,Type owner)
        {
            if (owner.IsInterface)
                return new DynamicMethod(name, returnType, parameterTypes, owner.Module, true);
            else
                return new DynamicMethod(name, returnType, parameterTypes, owner, true);
        }

        public static void GenerateCreateSetFieldIL(FieldInfo fieldInfo, ILGenerator il)
        {
            if (fieldInfo.IsStatic)
                il.PushInstance(fieldInfo.DeclaringType);
            il.Emit(OpCodes.Ldarg_1);
            il.UnboxIfNeeded(fieldInfo.FieldType);
            if (fieldInfo.IsStatic)
                il.Emit(OpCodes.Stsfld, fieldInfo);
            else
                il.Emit(OpCodes.Stfld, fieldInfo);
        }
    }
}

